# -*- coding: utf-8 -*-

from genetic_algorithm import genetic_algorithm
from tools import output_utls, input_utls , nfp_utls
from settings import SPACING, POPULATION_SIZE, MUTA_RATE, CURVETOLERANCE, BIN_LENGTH, BIN_WIDTH,generation_num, initial_rate
import json
from tools.nfp_utls import almost_equal, rotate_polygon, get_polygon_bounds, polygon_area,nfp_rectangle
import pyclipper
import copy
from material import Mater
import random

class Nester:
    def __init__(self, container=None, shapes=None):
        self.container = container
        self.shapes = shapes
        self.total_segments_area = 0

        self.nfp_cache = {}
        self.segment_cache = {}

        self.rotations = [0,90]
        self.spacing = SPACING

        self.GA = None
        self.generation = generation_num
        self.initial_rate = initial_rate

        self.best_fitness = 0
        self.best_population = None

        self.solution = None
        self.results = list()   # storage for the different results

    def set_segments(self, segments_lists):
        shapes_lst = []
        total_area = 0
        offset = self.spacing
        for segment_cord in segments_lists:
            p_id = segment_cord['p_id']
            texture = segment_cord['texture']
            length = segment_cord['length'] + offset
            width = segment_cord['width'] + offset

            area = length * width
            segment_shape = [[0, 0], [length, 0], [length, width], [0, width]]
            item = [{'x': p[0], 'y': p[1]} for p in segment_shape]
            shape = {'p_id': p_id,
                     'texture': texture,
                     'length': length,
                     'width': width,
                     'area': area,
                     'points': item
                     }
            total_area = total_area + area
            shapes_lst.append(shape)

        self.total_segments_area = total_area
        self.shapes_lst = shapes_lst
        assert 0 < self.initial_rate <=1
        self.container_num = int((self.total_segments_area/self.initial_rate)//(1220*2440)) + 1

    def set_container(self, bin_length, bin_width ):
        container = {}
        container_cord = [[0, 0], [bin_length, 0], [bin_length, bin_width], [0, bin_width]]
        container['p_id'] = '-1'
        container['length'] = bin_length
        container['width'] = bin_width
        container['area'] = bin_length * bin_width
        container['points'] = [{'x': p[0], 'y': p[1]} for p in container_cord]
        self.container = container

    def set_patches(self):
        pass

    def run(self):
        ##segments_sorted_list = sorted(self.shapes_lst, reverse=True, key=lambda o_segment:  max(o_segment['length'],o_segment['width']))
        segments_sorted_list = sorted(self.shapes_lst, reverse=True, key=lambda o_segment: o_segment['area'])
        print('Number of rectangle : ', len(segments_sorted_list))

        nfp_pairs = self.generate_nfp_paris(segments_sorted_list)
        self.nfp_cache = self.generate_nfp(nfp_pairs)
        segment_cache = {}
        for pairs in nfp_pairs:
            key = json.dumps(pairs['key'])
            segment_cache[key] = {'A': pairs['A'], 'B': pairs['B']}
        self.segment_cache = segment_cache

        if self.GA is None:
            container = self.container
            self.GA = genetic_algorithm.genetic_algorithm(segments_sorted_list, container)

        final_mater_lst = []
        current_container_num, mater_lst  = self.place_paths()
        if len( mater_lst ) > 0:
            final_mater_lst = [i for i in mater_lst]
        while current_container_num > 1:
            self.container_num = current_container_num - 1
            current_container_num,mater_lst = self.place_paths()
            if len( mater_lst ) > 0:
                final_mater_lst = [i for i in mater_lst]

        ## 打印最终排版结果
        if len(final_mater_lst) > 0:
            for i in range(len(final_mater_lst)):
                placed_segment_list_i = final_mater_lst[i].placed_segment_list
                pure_fitness = sum([p['length'] * p['width'] for p in placed_segment_list_i]) / (1220 * 2440)
                result_file_name = "L000" + str(i + 1) + '_' + str(len(placed_segment_list_i)) + '_' + str( round(pure_fitness, 3))
                rotated_shapes = [p['points'] for p in placed_segment_list_i]
                output_utls.output_result(final_mater_lst[i].movements_list, rotated_shapes, final_mater_lst[i].container, pure_fitness,result_file_name)
            print('consume of panel: ', len(final_mater_lst))
            print('total_fitness: ', self.total_segments_area / (1220 * 2440 * len(final_mater_lst)))

    ####################################################################################
    def place_paths(self):
        self.best_fitness = 0

        mater_lst = []
        for i in range(self.container_num):
            mat01 = Mater(i)
            mater_lst.append(mat01)

        generation = 0
        while generation < self.generation:
            print("generation = ", generation)
            pop_Size = len(self.GA.population)
            print('generation size :', pop_Size )
            for i in range(0, pop_Size):
                result_fitness, mater_lst = self.find_fitness(self.GA.population[i]['solution'], mater_lst)
                self.GA.population[i]['fitness'] = result_fitness
                if result_fitness > self.best_fitness:
                    self.best_fitness = result_fitness
                    self.best_population = self.GA.population[i]
                if self.best_fitness >= 1:
                    return len(mater_lst), mater_lst
                else:
                    for kk in range(len(mater_lst)):
                        mater_lst[kk].placed_segment_list = []
                        mater_lst[kk].movements_list = []

            self.GA.generation()
            generation = generation + 1
        return 0, []

    def find_fitness(self, solution , mater_lst):
        #### 开始计算 fitness
        ################################################################################################################
        temp_area = 0
        for i in range(len(solution)):
            segment = solution[i]
            max_use_ratio = 0
            max_j = None
            for j in range(len(mater_lst)):
                use_ratio = mater_lst[j].ponput_path(segment, self.nfp_cache, self.segment_cache)
                if use_ratio is not None and use_ratio > max_use_ratio:
                    max_use_ratio = use_ratio
                    max_j = j
            if max_use_ratio == 0:
                ##fitness = i/len(solution)
                fitness = temp_area/(1220*2440*self.container_num)
                ##fitness = temp_area/self.total_segments_area
                ##fitness = (i/len(solution) + temp_area/self.total_segments_area)/2
                print('fitness :', i, fitness)
                return fitness, mater_lst
            temp_area = temp_area + segment['area']
            for k in range(len(mater_lst)):
                if k != max_j:
                    mater_lst[k].placed_segment_list = [i for i in mater_lst[k].temp_placed_segment_list]
                    mater_lst[k].movements_list = [i for i in mater_lst[k].temp_movements_list]
            if i == len(solution) - 1:
                fitness = (i+1)/len(solution)
                print('Nesting succeed')
                print('fitness :', i+1, fitness)
                return fitness, mater_lst

    ####################################################################################
    def generate_nfp_paris(self, solution ):
        nfp_pairs = []
        for i in range(len(solution)):
            segment_i = solution[i]
            texture_i = segment_i['texture']
            segment_i_rota90 = rotate_polygon(segment_i, 90)
            segment_i_rota00 = rotate_polygon(segment_i, 0)
            container00 = rotate_polygon(self.container, 0)
            #############
            key = {'A': '-1', 'B': segment_i['p_id'], 'A_rotation': 0, 'B_rotation': 0}
            nfp_pairs.append({'A': container00, 'B': segment_i_rota00, 'key': key})
            #############
            if texture_i < 1 :
                key = {'A': '-1', 'B': segment_i['p_id'], 'A_rotation': 0, 'B_rotation': 90}
                nfp_pairs.append({'A': container00, 'B': segment_i_rota90, 'key': key})
            ##############################################################################
            for j in range(len(solution)):
                placed_segment_j = solution[j]
                texture_j = placed_segment_j['texture']
                placed_segment_j_rota90 = rotate_polygon(placed_segment_j, 90)
                placed_segment_j_rota00 = rotate_polygon(placed_segment_j, 0)
                #### segment_i 的 NFP
                key = {'A': placed_segment_j['p_id'], 'B': segment_i['p_id'], 'A_rotation': 0, 'B_rotation': 0}
                nfp_pairs.append({'A': placed_segment_j_rota00, 'B': segment_i_rota00, 'key': key})
                #############
                if texture_j < 1 :
                    key = {'A': placed_segment_j['p_id'], 'B': segment_i['p_id'], 'A_rotation': 90, 'B_rotation': 0}
                    nfp_pairs.append({'A': placed_segment_j_rota90, 'B': segment_i_rota00, 'key': key})
                #############
                if texture_i < 1 :
                    key = {'A': placed_segment_j['p_id'], 'B': segment_i['p_id'], 'A_rotation': 0, 'B_rotation': 90}
                    nfp_pairs.append({'A': placed_segment_j_rota00, 'B': segment_i_rota90, 'key': key})
                    #############
                    if texture_j < 1 :
                        key = {'A': placed_segment_j['p_id'], 'B': segment_i['p_id'], 'A_rotation': 90, 'B_rotation': 90}
                        nfp_pairs.append({'A': placed_segment_j_rota90, 'B': segment_i_rota90, 'key': key})
        return nfp_pairs

    def generate_nfp(self, nfp_pairs):
        """
        :param nfp_paris:
        :return:
        """
        nfp_list = []
        for pair in nfp_pairs:
            poly_a = pair['A']
            poly_b = pair['B']
            pair_a_id = pair['key']['A']
            if pair_a_id == '-1':
                nfp = nfp_rectangle(poly_a['points'], poly_b['points'])
                if nfp is not None and polygon_area(nfp) > 0:
                    nfp.reverse()
            else:
                nfp = minkowski_difference(poly_a, poly_b)  # 使用 Minkowski_difference和求两个零件的nfp, 考虑用lib
                if polygon_area(nfp) > 0:
                    nfp.reverse()
            nfp_list.append({'key': pair['key'], 'value': nfp})

        nfp_cache = {}
        for nfp in nfp_list:
            key = json.dumps(nfp['key'])
            nfp_cache[key] = nfp['value']

        return nfp_cache


def polygon_offset(polygon, offset, CURVETOLERANCE):
    """
    :param polygon: [{'x': , 'y': }...{'x': 'y': }]
    :param offset: 5
    :return:
    """
    polygon = [[p['x'], p['y']] for p in polygon]

    miter_limit = 2
    co = pyclipper.PyclipperOffset(miter_limit, CURVETOLERANCE)
    co.AddPath(polygon, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    result = co.Execute(1*offset)

    result = [{'x': p[0], 'y':p[1]} for p in result[0]]
    return result


def minkowski_difference(A, B):
    """
    两个多边形的相切空间
    http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/MinkowskiDiff.htm
    :param A:
    :param B:
    :return:
    """
    Ac = [[p['x'], p['y']] for p in A['points']]
    Bc = [[p['x'] * -1, p['y'] * -1] for p in B['points']]
    solution = pyclipper.MinkowskiSum(Ac, Bc, True)
    largest_area = None
    clipper_nfp = None
    for p in solution:
        p = [{'x': i[0], 'y':i[1]} for i in p]
        sarea = polygon_area(p)
        if largest_area is None or largest_area > sarea:
            clipper_nfp = p
            largest_area = sarea

    clipper_nfp = [{
                    'x': clipper_nfp[i]['x'] + Bc[0][0] * -1,
                    'y':clipper_nfp[i]['y'] + Bc[0][1] * -1
                   } for i in range(0, len(clipper_nfp))]
    return clipper_nfp


def minkow(A, B):
    Ac = [[p['x'], p['y']] for p in A]
    Bc = [[p['x'] * -1, p['y'] * -1] for p in B]

    solution = pyclipper.MinkowskiSum(Ac, Bc, True)
    largest_area = None
    clipper_nfp = None

    for p in solution:
        p = [{'x': i[0], 'y': i[1]} for i in p]
        sarea = nfp_utls.polygon_area(p)
        if largest_area is None or largest_area > sarea:
            clipper_nfp = p
            largest_area = sarea

    clipper_nfp = [{
        'x': clipper_nfp[i]['x'] + Bc[0][0] * -1,
        'y': clipper_nfp[i]['y'] + Bc[0][1] * -1
    } for i in range(0, len(clipper_nfp))]
    return clipper_nfp

